using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using System.Reflection;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace SBPweb.Data
{

	/// <summary>
	/// Implements basic functionallity of Entity
	/// </summary>
	public abstract class Entity: IEntity
	{

		#region Private fields

		private EntityActionState mAState = EntityActionState.Detached;
		private EntityTransactionState mTState;

		/// <summary>
		/// Values of entity
		/// </summary>
		protected Dictionary<string, object> mValues = new Dictionary<string, object>();
		
		private static Dictionary<Type, List<string>> mColumns = new Dictionary<Type, List<string>>();
        private static Dictionary<string, ConstructorInfo> mFactoryPrototype = new Dictionary<string, ConstructorInfo>();

		#endregion

        #region Constructor logic

		/// <summary>
		/// Initializes a new Entity instance
		/// </summary>
        public Entity()
        {
        }

		/// <summary>
		/// Initializes a new Entity instance from an entity instance
		/// </summary>
		/// <param name="ancestor">Ancestor of entity</param>
        public Entity(Entity ancestor)
        {
            this.mValues = ancestor.Values;
            this.ActionState = ancestor.ActionState;
            this.TransactionState = ancestor.TransactionState;
        }

        #endregion

        #region Public property accessors

		/// <summary>
		/// Returns EntityActionState of entity
		/// </summary>
        [SoapIgnore()]
        [XmlIgnore()]
		[SpecialName()]
		public virtual EntityActionState ActionState
		{
			get
			{
				return mAState;
			}
			set
			{
				mAState = value;
			}
		}

		/// <summary>
		/// Returns EntityTransactionState of entity
		/// </summary>
        [SoapIgnore()]
        [XmlIgnore()]
		[SpecialName()]
		public virtual EntityTransactionState TransactionState
		{
			get
			{
				return mTState;
			}
			set
			{
				mTState = value;
			}
		}

		/// <summary>
		/// Returns Values of entity (key is the name)
		/// </summary>
        [SoapIgnore()]
        [XmlIgnore()]
		[ReadOnly(true)]
		[SpecialName()]
		public Dictionary<string, object> Values
		{
			get
			{
				return mValues;
			}
		}
		
		/// <summary>
		/// Returns Columns of entity (key is the type)
		/// </summary>
        [SoapIgnore()]
        [XmlIgnore()]
		[ReadOnly(true)]
		public static Dictionary<Type, List<string>> Columns
		{
			get
			{
				return mColumns;
			}
		}

		/// <summary>
		/// Handler for TransactionState change
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="oldState"></param>
		/// <param name="newState"></param>
		public delegate void TransactionStateChanged(IEntity sender, EntityTransactionState oldState, EntityTransactionState newState);

		/// <summary>
		/// Fired when the TransactionState property changed
		/// </summary>
		public event TransactionStateChanged OnTransactionStateChanged;

		#endregion

		#region Static state management methods

		/// <summary>
		/// Sets the ActionState of the entity
		/// </summary>
		/// <param name="entity">IEntity entity</param>
		/// <param name="newState">EntityActionState newState</param>
		public static void SetActionState(IEntity entity, EntityActionState newState)
		{
			(entity as Entity).ActionState = newState;
		}

		/// <summary>
		/// Sets the TransactionState of the entity
		/// </summary>
		/// <param name="entity">IEntity entity</param>
		/// <param name="newState">EntityTransactionState newState</param>
		public static void SetTransactionState(IEntity entity, EntityTransactionState newState)
		{
			Entity e = (entity as Entity);
			if (e == null)
			{
				throw new ArgumentNullException("entity");
			}
			EntityTransactionState oldState = e.TransactionState;
			e.TransactionState = newState;
			if (e.OnTransactionStateChanged != null)
			{
				e.OnTransactionStateChanged.Invoke(entity, oldState, newState);
			}
			if (OnEntityTransactionStateChanged != null)
			{
				OnEntityTransactionStateChanged.Invoke(entity, oldState, newState);
			}
		}

		/// <summary>
		/// Fired when the TransactionState of an entity has changed
		/// </summary>
		public static event TransactionStateChanged OnEntityTransactionStateChanged;

		#endregion

		#region Static helper methods
        
		/// <summary>
		/// Registers the entity type public properties to columns dictionary
		/// </summary>
		/// <typeparam name="T">Entity type</typeparam>
        public static void RegisterEntityTypeToColumns<T>() where T : Entity
        {
            RegisterEntityTypeToColumns(typeof(T));
        }
		
		/// <summary>
		/// Registers the entity type public properties to columns dictionary
		/// </summary>
		/// <param name="entityType">Type entityType</param>
		public static void RegisterEntityTypeToColumns(Type entityType)
		{
            if (mColumns.ContainsKey(entityType))
			{
				return;
			}

			List<string> list = new List<string>();

            foreach (PropertyInfo pI in entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
			{
				if (pI.Attributes != PropertyAttributes.SpecialName && !list.Contains(pI.Name))// && !IsColumnNameAlreadyRegistered(type.UnderlyingSystemType, pI.Name))
			    {
                    list.Add(pI.Name);
				}
			}

            mColumns.Add(entityType, list);
		}

		/// <summary>
		/// Registers the constructor of the entity to FactoryPrototypes
		/// </summary>
		/// <typeparam name="T">Type of Entity</typeparam>
		/// <param name="entity">Type entity</param>
        public static void RegisterEntityTypeToFactory<T>(Type entity)
        {
            RegisterEntityTypeToFactory(entity, typeof(T).FullName);
        }

        /// <summary>
		/// Registers the constructor of the entity to FactoryPrototypes
		/// </summary>
		/// <param name="entityType">Type entityType</param>
		/// <param name="interfaceTypeName">string interfaceTypeName</param>
		public static void RegisterEntityTypeToFactory(Type entityType, string interfaceTypeName)
        {
            lock (mFactoryPrototype)
            {
                if (mFactoryPrototype.ContainsKey(interfaceTypeName))
                {
                    throw new ArgumentException(string.Format("InterfaceTypeName {0} already", interfaceTypeName));
                }
                ConstructorInfo cI = entityType.GetConstructor(new Type[] { typeof(Entity) });
                if (cI == null)
                {
                    throw new ArgumentException(string.Format("Type {0} does not have a constructor with the needed signature!", entityType));
                }
                mFactoryPrototype.Add(interfaceTypeName, cI);
            }
        }

        /// <summary>
        /// Clones an entity to type T
        /// </summary>
        /// <typeparam name="T">Type of result entity</typeparam>
        /// <param name="entity">entity to clone</param>
        /// <returns>Entity of type T</returns>
		public static T CreateInstanceClone<T>(Entity entity)
        {
            return CreateInstanceClone<T>(entity, typeof(T).FullName);
        }

		/// <summary>
		/// Clones an entity to type T
		/// </summary>
		/// <typeparam name="T">Type of result entity</typeparam>
		/// <param name="entity">entity to clone</param>
		/// <param name="interfaceTypeName">Type name of the interface of T</param>
		/// <returns>Entity of type T</returns>
		public static T CreateInstanceClone<T>(Entity entity, string interfaceTypeName)
        {
            lock (mFactoryPrototype)
            {
                if (!mFactoryPrototype.ContainsKey(interfaceTypeName))
                {
                    throw new ArgumentOutOfRangeException(interfaceTypeName);
                }

                ConstructorInfo cI = mFactoryPrototype[interfaceTypeName];
                Entity clone = (Entity)(cI.Invoke(new object[] { entity }));

                return (T)(IEntity)clone;
            }
        }

        #endregion

		#region Property accessors

		/// <summary>
		/// Returns the Value of a property
		/// </summary>
		/// <typeparam name="T">Type of property</typeparam>
		/// <param name="propertyName">Name of property</param>
		/// <returns>T</returns>
		public virtual T GetValue<T>(string propertyName)
		{
			if (!mValues.ContainsKey(propertyName))
			{
				return default(T);
			}
			return (T)mValues[propertyName];
		}

		/// <summary>
		/// Sets the Value of a property
		/// </summary>
		/// <typeparam name="T">Type of property</typeparam>
		/// <param name="propertyName">Name of property</param>
		/// <param name="value">Value of property</param>
		public virtual void SetValue<T>(string propertyName, T value)
		{
			mValues[propertyName] = value;
		}

		/// <summary>
		/// Sets the Value of a property and inserts it into the values dictionary 
		/// </summary>
		/// <typeparam name="T">Type of property</typeparam>
		/// <param name="propertyName">Name of property</param>
		/// <param name="value">Value of property</param>
		public virtual void SetValueEx<T>(string propertyName, T value)
		{
			if (!mValues.ContainsKey(propertyName))
			{
				mValues.Add(propertyName, value);
			}
			else
			{
				mValues[propertyName] = value;
			}
		}

		#endregion

		#region ICloneable Members

		/// <summary>
		/// Creates a clone of the entity
		/// </summary>
		/// <returns></returns>
		public abstract object Clone();

		#endregion

        #region Private helper functions

        private static bool IsColumnNameAlreadyRegistered(Type type, string name)
        {
            if (Entity.Columns.ContainsKey(type))
            {
                if (Entity.Columns[type].Contains(name))
                {
                    return true;
                }
            }
            if (Entity.Columns.ContainsKey(type.BaseType))
            {
                if (Entity.Columns[type.BaseType].Contains(name))
                {
                    return true;
                }
            }
            return false;
        }

        #endregion

    }
}
